﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace ClusteringWrraper
{
    public class FriendsMatrix
    {
        private readonly Dictionary<string, int> _uidToIndex;
        private readonly Dictionary<int, string> _indexToUid;
        private readonly Dictionary<int, FacebookUser> _indexToFbUser;
        private readonly int[][] _matrix;
        private readonly int _numOfFriends;
        private readonly object _asyncLock;
        private readonly int _myUid;
        private double _prec = 0.0001;
        private int _index;

        public FriendsMatrix(int numOfFriends, int myUid)
        {
            _numOfFriends = numOfFriends;
            _matrix = new int[_numOfFriends][];
            for (int i = 0; i < _numOfFriends; i++)
            {
                _matrix[i] = new int[_numOfFriends];
            }
            _uidToIndex = new Dictionary<string, int>(_numOfFriends);
            _indexToUid = new Dictionary<int, string>(_numOfFriends);
            _indexToFbUser = new Dictionary<int, FacebookUser>();
            _asyncLock = new object();
            _myUid = myUid;
        }

        public long RunAlgo(double prec)
        {
            _prec = prec;
            var timestamp = LoggedInUser.GetTimeStamp();
            ToFile(@"C:\IN\" + _myUid + ".in");
            mainFunc(prec, _myUid, timestamp);
            return timestamp;
        }

        public LinkedList<FriendsList> ParseAlgoOutput(long timestamp, out double deltaQ, out int numOfGroups)
        {
            var groupsList = new LinkedList<FriendsList>();
            TextReader tr = new StreamReader(String.Format("C:\\OUT\\{0}-{1}.out", _myUid, timestamp));
            string line;
            FacebookUser tmpUser;
            FriendsList fl;

            deltaQ = double.Parse(tr.ReadLine());
            numOfGroups = int.Parse(tr.ReadLine());

            while ((line = tr.ReadLine()) != null)
            {
                fl = new FriendsList(_prec);
                fl.Matrix = this;
                var tmpLine = line.Replace("  ", ",");
                foreach (var entry in tmpLine.Split(new[] { ',' }))
                {
                    if (string.IsNullOrEmpty(entry))
                        continue;

                    if (entry.StartsWith("***"))
                    {
                        fl.NumOfIter = Int32.Parse(entry.Replace("***", ""));
                        continue;
                    }

                    if (entry.StartsWith("###"))
                    {
                        fl.Grade = Double.Parse(entry.Replace("###", ""));
                        continue;
                    }

                    var tmpIndex = Int32.Parse(entry);
                    if (!_indexToFbUser.ContainsKey(tmpIndex))
                    {
                        continue;
                    }
                    tmpUser = _indexToFbUser[tmpIndex];
                    fl.AddUser(tmpUser);
                }
                groupsList.AddLast(fl);
            }
            return groupsList;
        }

        public void AddFbUserToMatrix(FacebookUser user)
        {
            _indexToFbUser.Add(_index, user);
            _indexToUid.Add(_index,user.Uid);
            _uidToIndex.Add(user.Uid, _index);
            _index++;
        }

        public void UpdateMatrix()
        {
            foreach (var user1 in _indexToFbUser.Values)
            {
                foreach (var user2 in user1.MutualFriendsUidToUser.Values)
                {
                    if (_uidToIndex.ContainsKey(user2.Uid))//Not all mutual friends are on the same cluster...
                    {
                        UpdateFriends(user1.Uid, user2.Uid);   
                    }
                }
            }
        }

        public void UpdateFriends(string uid1, string uid2)
        {
            var uid1Index = _uidToIndex[uid1];
            var uid2Index = _uidToIndex[uid2];

            lock (_asyncLock)
            {
                _matrix[uid1Index][uid2Index] = 1;
                _matrix[uid2Index][uid1Index] = 1;
            }
        }

        public void ToFile(string path)
        {
            TextWriter tw = new StreamWriter(path,false);
            tw.WriteLine(_numOfFriends);
            foreach (var col in _matrix)
            {
                foreach (var i in col)
                {
                    tw.Write(string.Format("{0} ", i));
                }
                tw.WriteLine();
            }
            tw.Close();
        }

        public bool AreFriends(FacebookUser user1, FacebookUser user2)
        {
            return (_uidToIndex.ContainsKey(user1.Uid) && 
                _uidToIndex.ContainsKey(user2.Uid) &&
                    _matrix[_uidToIndex[user1.Uid]][_uidToIndex[user2.Uid]] == 1);
        }

        public int GetIndexFromUser(FacebookUser user)
        {
            if (_uidToIndex.ContainsKey(user.Uid))
                return _uidToIndex[user.Uid];
            else
                return -1;
        }

        public FacebookUser GetUserFromIndex(int index)
        {
            if (index < _numOfFriends)
                return _indexToFbUser[index];
            else
                return null;
        }

        [DllImport("CClustering.dll")]
        static extern int mainFunc(double precision, int uid, long timestamp);
    }
}
